import json
import time
import os
import sys
import traceback

try:
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options as ChromeOptions
    from selenium.webdriver.chrome.service import Service as ChromeService
    from selenium.webdriver.firefox.options import Options as FirefoxOptions
    from selenium.webdriver.firefox.service import Service as FirefoxService
    from selenium.webdriver.edge.options import Options as EdgeOptions
    from selenium.webdriver.edge.service import Service as EdgeService
    from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    SELENIUM_AVAILABLE = True
except ImportError:
    SELENIUM_AVAILABLE = False
    print("警告：Selenium未安装，测试执行功能将不可用")

try:
    from ..app import app, TestResult, db
    MODELS_AVAILABLE = True
except ImportError:
    MODELS_AVAILABLE = False
    print("警告：数据库模型不可用")

from ..utils.driver_manager import DriverManager
from ..config.config import Config

class TestExecutor:
    def __init__(self, browser='edge'):
        if not SELENIUM_AVAILABLE:
            raise Exception("Selenium不可用，请安装selenium库")
        
        if not MODELS_AVAILABLE:
            raise Exception("数据库模型不可用")
        
        self.driver = None
        self.browser = browser
        self.driver_manager = DriverManager(browser)  # 将浏览器参数传递给DriverManager
        self.variables = {}  # 添加变量存储字典
        
    def execute_test_case(self, test_case):
        """
        执行单个测试用例（包括前置条件）
        """
        start_time = time.time()
        status = 'PASS'
        log = ''
        
        try:
            # 获取WebDriver实例
            self.driver = self.driver_manager.get_driver()
            self.wait = WebDriverWait(self.driver, 10)
            
            # 记录日志
            log += f"[INFO] 成功启动 {self.driver_manager.browser} 浏览器\n"
            
            # 收集所有前置条件（递归）
            log += f"[依赖检测] 开始解析 Case {test_case.id} 的前置链：\n"
            all_preconditions = self._collect_all_preconditions(test_case, [])
            
            # 将前置条件信息添加到日志中
            for precondition in all_preconditions:
                log += f"  - 依赖 Case {precondition.id} ({precondition.name})\n"
            
            # 显示执行顺序
            if all_preconditions:
                log += "\n[执行顺序]\n"
                for i, precondition in enumerate(all_preconditions):
                    log += f"{i+1}. 执行 Case {precondition.id}: {precondition.name}\n"
                log += f"{len(all_preconditions)+1}. 执行 Case {test_case.id}: {test_case.name}\n"
            
            # 按顺序执行所有前置条件
            executed_preconditions = []
            for precondition in all_preconditions:
                if not precondition.is_deleted:
                    log += f"\n[执行开始] Case ID:{precondition.id} | Name:{precondition.name}\n"
                    precondition_status, precondition_log = self._execute_single_test_case(precondition)
                    log += precondition_log
                    executed_preconditions.append({
                        'id': precondition.id,
                        'name': precondition.name,
                        'status': precondition_status
                    })
                    
                    if precondition_status != 'PASS':
                        status = f'Blocked by Case {precondition.id}'
                        log += f"[执行完成] Case ID:{precondition.id} | 状态:失败\n"
                        break
                    else:
                        log += f"[执行完成] Case ID:{precondition.id} | 状态:成功\n"
            
            # 如果所有前置条件都执行成功，再执行当前测试用例
            if status == 'PASS':
                log += f"\n[执行开始] Case ID:{test_case.id} | Name:{test_case.name}\n"
                case_status, case_log = self._execute_single_test_case(test_case)
                log += case_log
                status = case_status
                log += f"[执行完成] Case ID:{test_case.id} | 状态:{status}\n"
            
        except Exception as e:
            status = 'ERROR'
            log += f"[ERROR] 执行失败: {str(e)}\n"
            if "驱动" in str(e) or "driver" in str(e).lower():
                log += "[SOLUTION] 请尝试以下解决方案：\n"
                log += "1. 检查网络连接\n"
                log += "2. 配置系统代理（如果使用代理）\n"
                log += "3. 手动下载并配置相应浏览器驱动\n"
                log += "4. 临时使用其他浏览器进行测试\n"
            
        finally:
            # 计算执行时间
            execution_time = time.time() - start_time
            
            # 保存截图
            screenshot_path = None
            if self.driver:
                try:
                    # 使用配置文件中定义的截图目录
                    screenshot_dir = Config.SCREENSHOTS_FOLDER
                    if not os.path.exists(screenshot_dir):
                        os.makedirs(screenshot_dir)
                    
                    # 生成截图文件名
                    timestamp = int(time.time())
                    filename = f"screenshot_{test_case.id}_{timestamp}.png"
                    screenshot_path = os.path.join(screenshot_dir, filename)
                    
                    # 保存截图
                    self.driver.save_screenshot(screenshot_path)
                    log += f"[INFO] 截图已保存: {filename}\n"
                except Exception as e:
                    log += f"[WARN] 保存截图失败: {str(e)}\n"
            
            # 关闭浏览器
            try:
                if self.driver_manager:
                    self.driver_manager.quit_driver()
            except Exception as e:
                log += f"[WARN] 关闭浏览器失败: {str(e)}\n"
        
        # 创建测试结果对象
        result = TestResult(
            test_case_id=test_case.id,
            status=status,
            log=log,
            execution_time=execution_time,
            screenshot_path='screenshots/' + os.path.basename(screenshot_path) if screenshot_path else None
        )
        
        # 保存到数据库
        if MODELS_AVAILABLE:
            try:
                db.session.add(result)
                db.session.commit()
            except Exception as e:
                print(f"保存测试结果到数据库失败: {e}")
        
        return result
    
    def _collect_all_preconditions(self, test_case, visited):
        """
        递归收集所有前置条件，按执行顺序返回
        """
        preconditions = []
        
        # 检查是否存在前置条件
        if hasattr(test_case, 'precondition_test_cases'):
            for precondition in test_case.precondition_test_cases:
                # 避免循环依赖
                if precondition.id not in visited:
                    new_visited = visited + [precondition.id]
                    # 递归获取更深层的前置条件
                    deep_preconditions = self._collect_all_preconditions(precondition, new_visited)
                    preconditions.extend(deep_preconditions)
                    
                    # 添加当前前置条件
                    preconditions.append(precondition)
                # 注意：这里我们不记录循环依赖日志，因为visited列表是独立的每次调用
        
        return preconditions
    
    def _execute_single_test_case(self, test_case):
        """
        执行单个测试用例（不包括前置条件）
        """
        status = 'PASS'
        log = ''
        
        try:
            # 如果测试用例指定了URL，则导航到该URL
            if test_case.url:
                log += f"[INFO] 导航到URL: {test_case.url}\n"
                self.driver.get(test_case.url)
                log += f"[INFO] 当前页面URL: {self.driver.current_url}\n"
            
            # 解析测试步骤
            steps = json.loads(test_case.steps) if isinstance(test_case.steps, str) else test_case.steps
            
            # 执行每个测试步骤
            for i, step in enumerate(steps):
                try:
                    log += f"[步骤{i+1}] {step.get('action', 'unknown action')}\n"
                    step_log = self._execute_step(step)  # 获取步骤执行返回的日志
                    log += step_log  # 将步骤日志添加到总日志中
                except Exception as e:
                    status = 'FAIL'
                    log += f"[ERROR] 步骤 {i+1} 执行失败: {str(e)}\n"
                    log += f"[DEBUG] 当前页面URL: {self.driver.current_url}\n"
                    # 发生错误时继续执行其他步骤
                    continue
                    
        except Exception as e:
            status = 'ERROR'
            log += f"[ERROR] 执行失败: {str(e)}\n"
        
        return status, log
    
    def _execute_step(self, step):
        """
        执行单个测试步骤
        """
        action = step.get('action')
        selector_type = step.get('selector_type')
        selector_value = step.get('selector_value')
        value = step.get('value')
        log = ''  # 添加日志变量
        
        # 替换变量占位符
        selector_value = self._replace_variables(selector_value)
        value = self._replace_variables(value)
        
        # 检查是否需要等待
        if action != 'wait':
            # 确保页面已加载
            WebDriverWait(self.driver, 10).until(
                lambda driver: driver.execute_script("return document.readyState") == "complete"
            )
        
        if action == 'navigate':
            log_detail = f"导航到 {value}"
            self.driver.get(value)
            
        elif action == 'click':
            log_detail = f"点击元素 ({selector_type}={selector_value})"
            locator = self._get_locator(selector_type, selector_value)
            element = self.wait.until(EC.element_to_be_clickable(locator))
            element.click()
            
        elif action == 'input':
            log_detail = f"在元素 ({selector_type}={selector_value}) 中输入 '{value}'"
            locator = self._get_locator(selector_type, selector_value)
            element = self.wait.until(EC.presence_of_element_located(locator))
            element.clear()
            element.send_keys(value)
            
        elif action == 'wait':
            log_detail = f"等待 {value} 秒"
            time.sleep(float(value))
            
        elif action == 'assert_text':
            assert_type = step.get('assert_type', 'contains')
            assert_scope = step.get('assert_scope', 'element')
            
            if assert_scope == 'page':
                log_detail = f"断言页面文本 {assert_type} '{value}'"
                page_text = self.driver.find_element(By.TAG_NAME, 'body').text
                self._perform_assertion(page_text, value, assert_type)
            else:  # element
                log_detail = f"断言元素 ({selector_type}={selector_value}) 文本 {assert_type} '{value}'"
                locator = self._get_locator(selector_type, selector_value)
                element = self.wait.until(EC.presence_of_element_located(locator))
                element_text = element.text
                self._perform_assertion(element_text, value, assert_type)
        
        elif action == 'assert_variable':
            assert_type = step.get('assert_type', 'equals')
            expected_value = step.get('value')
            
            # 获取变量名的逻辑
            # 1. 如果有自定义变量名且不为空，优先使用
            # 2. 如果只有预定义变量，使用预定义变量
            # 3. 如果两者都有且相同，说明是预定义变量
            # 4. 如果两者都有且不同，优先使用自定义变量名
            variable_name = step.get('variable_name')
            variable_select = step.get('variable_select')
            
            # 确定最终使用的变量名
            if variable_name and variable_select and variable_name != variable_select:
                # 有自定义变量名且与选择的变量不同，使用自定义变量名
                final_variable_name = variable_name
            elif variable_name and variable_select and variable_name == variable_select:
                # 两者相同，说明是预定义变量
                final_variable_name = variable_select
            elif variable_name and not variable_select:
                # 只有自定义变量名
                final_variable_name = variable_name
            elif not variable_name and variable_select:
                # 只选择了预定义变量
                final_variable_name = variable_select
            else:
                # 两者都为空
                final_variable_name = None
            
            if not final_variable_name:
                raise ValueError("未指定要断言的变量名")
            
            if final_variable_name not in self.variables:
                raise ValueError(f"变量 '{final_variable_name}' 不存在")
            
            actual_value = self.variables[final_variable_name]
            log_detail = f"断言变量 '{final_variable_name}' {assert_type} '{expected_value}'"
            log += f"[INFO] 变量实际值: {actual_value}\n"
            self._perform_assertion(str(actual_value), expected_value, assert_type)
        
        elif action == 'get_title':
            log_detail = f"获取页面标题"
            title = self.driver.title
            log += f"[INFO] 当前页面标题: {title}\n"
            # 存储变量供后续步骤使用
            variable_name = step.get('variable_name', 'title')
            self._store_variable(variable_name, title)
            
        elif action == 'get_text':
            log_detail = f"获取元素 ({selector_type}={selector_value}) 的文本"
            locator = self._get_locator(selector_type, selector_value)
            element = self.wait.until(EC.presence_of_element_located(locator))
            text = element.text
            log += f"[INFO] 元素文本: {text}\n"
            # 存储变量供后续步骤使用
            variable_name = step.get('variable_name', 'text')
            self._store_variable(variable_name, text)
            
        elif action == 'get_attribute':
            attribute_name = step.get('attribute_name', 'value')  # 默认获取value属性
            log_detail = f"获取元素 ({selector_type}={selector_value}) 的 '{attribute_name}' 属性值"
            locator = self._get_locator(selector_type, selector_value)
            element = self.wait.until(EC.presence_of_element_located(locator))
            attribute_value = element.get_attribute(attribute_name)
            log += f"[INFO] 元素 '{attribute_name}' 属性值: {attribute_value}\n"
            # 存储变量供后续步骤使用
            variable_name = step.get('variable_name', attribute_name)
            self._store_variable(variable_name, attribute_value)
            
        elif action == 'get_window_handles':
            log_detail = "获取所有窗口句柄"
            handles = self.driver.window_handles
            current_handle = self.driver.current_window_handle
            log += f"[INFO] 当前窗口句柄: {current_handle}\n"
            log += f"[INFO] 所有窗口句柄: {handles}\n"
            # 存储变量供后续步骤使用
            variable_name = step.get('variable_name', 'window_handles')
            self._store_variable(variable_name, handles)
            self._store_variable("current_window_handle", current_handle)
            
        elif action == 'switch_to_window':
            # 处理window_handles[-1]或wh[-1]这样的表达式
            import re
            # 匹配 "变量名[索引]" 格式的表达式
            match = re.match(r'^([a-zA-Z_]\w*)\[(\-?\d+)\]$', value.strip())
            if match:
                var_name = match.group(1)
                index = int(match.group(2))
                
                # 获取存储的变量
                if var_name in self.variables:
                    var_value = self.variables[var_name]
                    if isinstance(var_value, list) and len(var_value) > 0:
                        # 处理负数索引
                        if index < 0:
                            index = len(var_value) + index
                        # 检查索引是否有效
                        if 0 <= index < len(var_value):
                            value = var_value[index]
                        else:
                            raise ValueError(f"索引 {match.group(2)} 超出范围，变量 {var_name} 只有 {len(var_value)} 个元素")
                    else:
                        raise ValueError(f"变量 {var_name} 不是有效的列表")
                else:
                    raise ValueError(f"未找到变量 {var_name}")
            else:
                # 替换变量占位符
                value = self._replace_variables(value)
                
            log_detail = f"切换到窗口句柄: {value}"
            self.driver.switch_to.window(value)
            log += f"[INFO] 当前窗口句柄: {self.driver.current_window_handle}\n"
            
        elif action == 'close_window':
            log_detail = "关闭当前窗口"
            self.driver.close()
            # 切换到下一个可用的窗口
            if len(self.driver.window_handles) > 0:
                self.driver.switch_to.window(self.driver.window_handles[0])
            log += f"[INFO] 剩余窗口数量: {len(self.driver.window_handles)}\n"
        
        # 记录执行的详细信息
        print(f"[执行详情] {log_detail}")
        return log  # 返回日志
    
    def _get_locator(self, selector_type, selector_value):
        """
        根据选择器类型和值返回定位器
        """
        if selector_type == 'id':
            return (By.ID, selector_value)
        elif selector_type == 'name':
            return (By.NAME, selector_value)
        elif selector_type == 'class':
            return (By.CLASS_NAME, selector_value)
        elif selector_type == 'xpath':
            return (By.XPATH, selector_value)
        elif selector_type == 'css':
            return (By.CSS_SELECTOR, selector_value)
        else:
            raise ValueError(f"不支持的选择器类型: {selector_type}")
    
    def _perform_assertion(self, actual_text, expected_text, assert_type):
        """
        执行断言
        """
        if assert_type == 'contains':
            assert expected_text in actual_text, f"期望文本 '{expected_text}' 未在实际文本 '{actual_text}' 中找到"
        elif assert_type == 'equals':
            assert actual_text == expected_text, f"实际文本 '{actual_text}' 与期望文本 '{expected_text}' 不相等"
        elif assert_type == 'not_contains':
            assert expected_text not in actual_text, f"期望文本 '{expected_text}' 在实际文本 '{actual_text}' 中被找到"
        elif assert_type == 'not_equals':
            assert actual_text != expected_text, f"实际文本 '{actual_text}' 与期望文本 '{expected_text}' 相等"
        elif assert_type == 'starts_with':
            assert actual_text.startswith(expected_text), f"实际文本 '{actual_text}' 未以期望文本 '{expected_text}' 开头"
        elif assert_type == 'ends_with':
            assert actual_text.endswith(expected_text), f"实际文本 '{actual_text}' 未以期望文本 '{expected_text}' 结尾"
        else:
            raise ValueError(f"不支持的断言类型: {assert_type}")
    
    def _store_variable(self, name, value):
        """存储变量供后续步骤使用"""
        self.variables[name] = value
        print(f"[变量存储] {name} = {value}")
        
    def _get_variable(self, name):
        """获取存储的变量值"""
        return self.variables.get(name)
        
    def _replace_variables(self, text):
        """替换文本中的变量占位符"""
        if not text or not isinstance(text, str):
            return text
            
        import re
        # 匹配 ${variable_name} 格式的变量
        pattern = r'\$\{([^}]+)\}'
        matches = re.findall(pattern, text)
        
        result = text
        for match in matches:
            variable_name = match.strip()
            if variable_name in self.variables:
                variable_value = str(self.variables[variable_name])
                result = result.replace(f'${{{variable_name}}}', variable_value)
                
        return result

    def execute_test_suite(self, test_suite):
        """
        执行测试套件，每条测试用例独立执行（独立的浏览器实例）
        """
        start_time = time.time()
        status = 'PASS'
        log = ''
        
        try:
            # 记录日志
            log += f"[INFO] 开始执行测试套件: {test_suite.name}\n"
            
            # 执行测试套件中的每个测试用例（独立执行）
            executed_test_cases = []
            test_cases = test_suite.test_cases if hasattr(test_suite, 'test_cases') else []
            
            for i, test_case in enumerate(test_cases):
                if test_case.is_deleted:
                    log += f"[跳过] 测试用例 '{test_case.name}' 已被删除\n"
                    continue
                
                log += f"\n[执行开始] Case ID:{test_case.id} | Name:{test_case.name}\n"
                
                # 为每个测试用例创建独立的执行器实例
                case_executor = TestExecutor(self.browser)
                try:
                    # 执行单个测试用例
                    case_result = case_executor.execute_test_case(test_case)
                    executed_test_cases.append(case_result)
                    
                    # 记录测试用例执行结果
                    log += case_result.log
                    log += f"[执行完成] Case ID:{test_case.id} | 状态:{case_result.status}\n"
                    
                    # 如果测试用例失败，更新套件状态
                    if case_result.status != 'PASS' and status == 'PASS':
                        status = 'FAIL'
                except Exception as e:
                    # 捕获单个用例执行异常，避免影响其他用例执行
                    case_status = 'ERROR'
                    case_log = f"[ERROR] 执行失败: {str(e)}\n"
                    case_log += f"[DEBUG] 异常详情: {traceback.format_exc()}\n"
                    
                    log += case_log
                    log += f"[执行完成] Case ID:{test_case.id} | 状态:ERROR\n"
                    
                    # 创建一个简单的结果对象用于记录
                    from ..app import TestResult
                    case_result = TestResult(
                        test_case_id=test_case.id,
                        status=case_status,
                        log=case_log,
                        execution_time=0
                    )
                    executed_test_cases.append(case_result)
                    
                    if status == 'PASS':
                        status = 'FAIL'
                finally:
                    # 确保每个执行器的驱动都被关闭
                    try:
                        if case_executor.driver_manager:
                            case_executor.driver_manager.quit_driver()
                    except Exception as e:
                        log += f"[WARN] 关闭测试用例 {test_case.id} 的浏览器失败: {str(e)}\n"
            
            log += f"\n[INFO] 测试套件执行完成，共执行 {len(executed_test_cases)} 个测试用例\n"
            
        except Exception as e:
            status = 'ERROR'
            log += f"[ERROR] 执行测试套件失败: {str(e)}\n"
            log += f"[DEBUG] 异常详情: {traceback.format_exc()}\n"
            if "驱动" in str(e) or "driver" in str(e).lower():
                log += "[SOLUTION] 请尝试以下解决方案：\n"
                log += "1. 检查网络连接\n"
                log += "2. 配置系统代理（如果使用代理）\n"
                log += "3. 手动下载并配置相应浏览器驱动\n"
                log += "4. 临时使用其他浏览器进行测试\n"
            
        finally:
            # 计算执行时间
            execution_time = time.time() - start_time
            
            # 保存截图（使用当前执行器的驱动，如果还存在的话）
            screenshot_path = None
            if self.driver:
                try:
                    # 使用配置文件中定义的截图目录
                    screenshot_dir = Config.SCREENSHOTS_FOLDER
                    if not os.path.exists(screenshot_dir):
                        os.makedirs(screenshot_dir)
                    
                    # 生成截图文件名
                    timestamp = int(time.time())
                    filename = f"suite_{test_suite.id}_{timestamp}.png"
                    screenshot_path = os.path.join(screenshot_dir, filename)
                    
                    # 保存截图
                    self.driver.save_screenshot(screenshot_path)
                    log += f"[INFO] 截图已保存: {filename}\n"
                except Exception as e:
                    log += f"[WARN] 保存截图失败: {str(e)}\n"
            
            # 关闭当前执行器的浏览器（如果还未关闭）
            try:
                if self.driver_manager:
                    self.driver_manager.quit_driver()
            except Exception as e:
                log += f"[WARN] 关闭浏览器失败: {str(e)}\n"
        
        # 创建测试结果对象
        result = {
            'test_suite': {
                'id': test_suite.id,
                'name': test_suite.name
            },
            'status': status,
            'log': log,
            'execution_time': execution_time,
            'executed_at': time.strftime('%Y-%m-%dT%H:%M:%S'),
            'screenshot_path': 'screenshots/' + os.path.basename(screenshot_path) if screenshot_path else None
        }
        
        return result
